package com.xbongbong.pro.statistic.service.result;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.i18n.util.I18nMessageUtil;
import com.xbongbong.paas.constant.SizeConstant;
import com.xbongbong.paas.domain.entity.PaasStageEntity;
import com.xbongbong.paas.domain.entity.ext.PaasFormDataEntityExt;
import com.xbongbong.paas.elasticsearch.pojo.AggMorePojo;
import com.xbongbong.paas.elasticsearch.pojo.AggSubFactoriesPojo;
import com.xbongbong.paas.enums.FieldTypeEnum;
import com.xbongbong.paas.enums.IndexTypeEnum;
import com.xbongbong.paas.enums.StageTypeEnum;
import com.xbongbong.paas.field.ItemPoJo;
import com.xbongbong.paas.pojo.StageFindPojo;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.BeanUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.constant.CommonConstant;
import com.xbongbong.pro.constant.ElasticConstant;
import com.xbongbong.pro.constant.I18nStringConstant;
import com.xbongbong.pro.domain.entity.ChartEntity;
import com.xbongbong.pro.service.stage.StageFindStrategy;
import com.xbongbong.pro.service.stage.factory.StageFindFactory;
import com.xbongbong.pro.service.toolbox.statistic.util.ChartResultUtil;
import com.xbongbong.pro.statistic.constant.ChartI18Constant;
import com.xbongbong.pro.statistic.enums.NumFormatEnum;
import com.xbongbong.pro.statistic.enums.PanoramaEnum;
import com.xbongbong.pro.statistic.enums.SystemCodeEnum;
import com.xbongbong.pro.statistic.enums.ValueTypeEnum;
import com.xbongbong.pro.statistic.pojo.TimePojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultPkPojo;
import com.xbongbong.pro.statistic.result.pojo.ChartResultPojo;
import com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo;
import com.xbongbong.pro.statistic.result.pojo.FloorAggsKeyPojo;
import com.xbongbong.pro.statistic.result.pojo.PanoramaPojo;
import com.xbongbong.pro.statistic.result.pojo.StageFunnelInfoPojo;
import com.xbongbong.pro.statistic.result.pojo.StageFunnelPojo;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultInfoDTO;
import com.xbongbong.pro.statistic.result.pojo.dto.StatisticResultPieInfoDTO;
import com.xbongbong.pro.statistic.result.pojo.vo.ChartResultDetailVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ChartResultPkDetailVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultChartVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataSeriesChartVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultDataTableVO;
import com.xbongbong.pro.statistic.result.pojo.vo.ResultTableVO;
import com.xbongbong.pro.statistic.through.pojo.dto.ThroughDTO;
import com.xbongbong.saas.constant.BasicConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.ChartConfigEnum;
import com.xbongbong.saas.enums.business.CustomerCommunicateEnum;
import com.xbongbong.saas.enums.business.SalesOpportunityEnum;
import com.xbongbong.saas.enums.dictionary.DictionaryEnum;
import com.xbongbong.saas.model.OpportunityStageLogModel;
import com.xbongbong.saas.model.SaleStageModel;
import com.xbongbong.saas.service.toolbox.help.ScriptHelp;
import com.xbongbong.sys.domain.entity.UserEntity;
import org.apache.commons.collections4.CollectionUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static org.elasticsearch.index.query.QueryBuilders.*;

/**
 * 机会相关统计
 * @author zcp
 * @version v1.0
 * @date 2019/2/26 21:43
 * @since v1.0
 */
@Service
public class CrmOpportunityResult extends ChartResultParentService implements ChartResultSystem, ChartResultPk, PanoramaResult {
    private static final Logger LOG = LoggerFactory.getLogger(CrmOpportunityResult.class);
    @Resource
    private SaleStageModel saleStageModel;
    @Autowired
    private OpportunityStageLogModel opportunityStageLogModel;

    @Override
    public Object getTarget(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            case OPPORTUNITY_FUNNEL_HOME://首页-销售漏斗 301_05_01,
                return opportunityFunnelHome(chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_FUNNEL_CHART: // 图表中心-销售漏斗 301_05_02
                return opportunityFunnelChart(chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_ADD_CHART:
                return opportunityChart(true, chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_ADD_COUNT_HOME:
                return opportunityAddHome(true, chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_ADD_AMOUNT_HOME:
                return opportunityAddHome(false, chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_EXPIRE_COUNT_HOME:
                return opportunityExpireHome(true, chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_EXPIRE_AMOUNT_HOME:
                return opportunityExpireHome(false, chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_ESTIMATE_CHART:
                return opportunityChart(false, chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_WIN_RATE_CHART:
                return opportunityWinRateChart(chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_MAP_CHART:
                return opportunityMapChart(chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_LOSE_CAUSE_CHART:
                return opportunityLoseCauseChart(chartResultPojo, chartResultDetailVO);
            case OPPORTUNITY_STAGE_CHART:
                return opportunityStageChart(chartResultPojo, chartResultDetailVO);
            default:
                break;
        }
        return null;
    }

    @Override
    public Object getValueTarget(ChartResultPojo chartResultPojo) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            case OPPORTUNITY_ADD_COUNT_BOARD:
                //新增机会数
                return opportunityAddBoard(true, chartResultPojo);
            case OPPORTUNITY_ADD_AMOUNT_BOARD:
                //新增机会金额
                return opportunityAddBoard(false, chartResultPojo);
            case OPPORTUNITY_FOLLOW_BOARD:
                return opportunityFollowBoard(chartResultPojo);
            case OPPORTUNITY_CUSTOMER_BOARD:
                return opportunityCustomerBoard(chartResultPojo);
            case OPPORTUNITY_WIN_COUNT:
                return opportunityWin(true, chartResultPojo);
            case OPPORTUNITY_WIN_AMOUNT:
                return opportunityWin(false, chartResultPojo);
            default:
                break;
        }
        return null;
    }

    @Override
    public Object getPkTarget(UserEntity userEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPkPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            //pk、完成情况（员工排行）
            case PK_OPPORTUNITY_ADD_COUNT:
            case PERFORMANCE_OPPORTUNITY_ADD_COUNT:
                return pkOpportunityAddCount(userEntity, chartResultPkPojo, chartResultPkDetailVO);
            case PK_OPPORTUNITY_ADD_AMOUNT:
            case PERFORMANCE_OPPORTUNITY_ADD_AMOUNT:
                return pkOpportunityAddAmount(userEntity, chartResultPkPojo, chartResultPkDetailVO);
            default:
                break;
        }
        return null;
    }

    /**
     * 系统pk范围规则指标公共接口
     *
     * @param userEntity            登录员工
     * @param chartEntity
     * @param chartResultPkPojo     pk参数
     * @param chartResultPkDetailVO 塞入pk结果
     * @return java.lang.Object
     * @throws XbbException
     * @author zcp
     * @date 2019/3/18 15:34
     * @version v1.0
     * @since v1.0
     */
    @Override
    public Object getPkRuleTarget(UserEntity userEntity, ChartEntity chartEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        SystemCodeEnum systemCodeEnum = chartResultPkPojo.getSystemCodeEnum();
        switch (systemCodeEnum) {
            //pk、完成情况（员工排行）
            case PK_OPPORTUNITY_ADD_COUNT:
            case PERFORMANCE_OPPORTUNITY_ADD_COUNT:
                return pkRuleOpportunityAddCount(userEntity, chartEntity, chartResultPkPojo, chartResultPkDetailVO);
            case PK_OPPORTUNITY_ADD_AMOUNT:
            case PERFORMANCE_OPPORTUNITY_ADD_AMOUNT:
                return pkRuleOpportunityAddAmount(userEntity, chartEntity, chartResultPkPojo, chartResultPkDetailVO);
            default:
                break;
        }
        return null;
    }

    @Override
    public Object customer(PanoramaPojo panoramaPojo) throws XbbException {
        PanoramaEnum panoramaEnum = panoramaPojo.getPanoramaEnum();
        switch (panoramaEnum) {
            case CUSTOMER_OPPORTUNITY_COUNT:
                return panoramaCustomerOpportunity(true, panoramaPojo);
            case CUSTOMER_OPPORTUNITY_AMOUNT:
                return panoramaCustomerOpportunity(false, panoramaPojo);
            default:
                break;
        }
        return null;
    }

    /**
     * 首页机会漏斗
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object 
     * @throws XbbException
     * @author zcp
     * @date 2019/3/10 18:03
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityFunnelHome(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        //阶段：0其他普通类型，1赢单，2输单，3取消，不统计输单、取消
        //List<Integer> noStatisticsCodeIn = SaleStageEnum.getCode4EstimateNoStatistics();
        //阶段id--该阶段对应的机会数、预计金额Object
        StageFunnelPojo stageFunnelPojo = getFunnel4Opportunity(chartResultPojo);
        //首页-销售漏斗: "图"不需要展示"阶段-平均停留时长","表格"只展示"阶段名称"，"数量","金额"，所以下面这个方法没有做特殊处理
        return packageFunnel(chartResultPojo, chartResultDetailVO, stageFunnelPojo.getSaleStageList(), stageFunnelPojo);
    }
    /**
     * 图表中心---机会阶段漏斗：按照预计结束时间统计
     * 数量，金额，预测金额
     * 销售机阶段转化率（排除取消和输单阶段，只包含普通和赢单阶段）：该阶段后面所有阶段机会总数除以 从当前阶段开始的所有机会数 作为该阶段的转化率。
     * 比如，假设总共有5个阶段，阶段1的转化率=阶段(2+3+4+5)数/5个阶段总数；阶段2的转化率=(3+4+5)数/5个阶段总数，以此类推
     * 分两种情况：
     * （1）还处于该阶段，则按照截至到nowTime（当前时间）减去进入该阶段的时间，差值即停留时长；
     * （2）已经改到下面的阶段，则直接按照离开阶段的时间减去进入到该阶段时的时间，差值即停留时长。
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object 
     * @throws XbbException
     * @author zcp
     * @date 2019/3/10 16:28
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityFunnelChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        //List<Integer> noStatisticsCodeIn = SaleStageEnum.getCode4EstimateNoStatistics();
        //获取特定的机会阶段（机会formId）;  2020/8/21 之后需求： 漏斗增加了“输单”，“取消”两个阶段，所以noStatisticsCodeIn设为null（目前只要销售漏斗的图表数据是调用这个方法，所以noStatisticsCodeIn值为空不为影响其他）
        //阶段id--该阶段对应的机会数、预计金额Object
        StageFunnelPojo stageFunnelPojo = getFunnel4Opportunity(chartResultPojo);
        //图表中心的销售漏斗 比 首页的销售漏斗 要多展示的是：(1)图数据需要展示"阶段-平均停留时间"(2)表格展示字段更多（图表中心7个，首页只展示3个字段）
        //阶段code--平均停留时长
        Map<String, Double> dayMap = getAverageStayTime(chartResultPojo, stageFunnelPojo.getCalcStayStageCodeIn());
        return packageFunnelFull(chartResultPojo, chartResultDetailVO, stageFunnelPojo.getSaleStageList(), stageFunnelPojo, dayMap);
    }
    /**
     * 新增机会金额与数理趋势分析、机会金额与数量预计趋势分析
     * 封装一张图上两张线（金额、数量）：新增机会趋势图、预计机会趋势图
     * @param addFlag 是否是新增，true新增；false预计
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object
     * @throws XbbException
     * @author zcp
     * @date 2019/2/27 19:01
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityChart(boolean addFlag, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        ElasticAggsPojo amountAggsPojo;
        ElasticAggsPojo countAggsPojo;
        if (addFlag) {
            amountAggsPojo = opportunityAdd2ElasticAggsPojo(false, chartResultPojo, ValueTypeEnum.ARRAY_TYPE);
            countAggsPojo = opportunityAdd2ElasticAggsPojo(true, chartResultPojo, ValueTypeEnum.ARRAY_TYPE);
            //分别采用两个不同单位的y轴，默认为yAxisIndex: 0，当为1时，代表另外一个y坐标轴
            Integer yAxisIndex = 0;
        }else {
            amountAggsPojo = opportunityEstimate2ElasticAggsPojo(false, chartResultPojo, ValueTypeEnum.ARRAY_TYPE);
            countAggsPojo = opportunityEstimate2ElasticAggsPojo(true, chartResultPojo, ValueTypeEnum.ARRAY_TYPE);
        }
        ThroughDTO throughDTO = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        //是否画入表格内 0否1是
        Integer tableFlag = 1;
        Boolean totalFlag = false;
        //分别采用两个不同单位的y轴，默认为yAxisIndex: 0，当为1时，代表另外一个y坐标轴
        Integer yAxisIndex = 0;
        Integer hide = 0;
        String legend = PanoramaEnum.CUSTOMER_OPPORTUNITY_COUNT.getName();
        String unit = systemChartHelp.getUnit(I18nMessageUtil.getMessage(CommonConstant.EACH));
        StatisticResultInfoDTO amountStatisticResultInfoDto = new StatisticResultInfoDTO(throughDTO, tableFlag, yAxisIndex, hide, legend, unit, countAggsPojo);
        yAxisIndex = 1;
        legend = I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_AMOUNT);
        unit = systemChartHelp.getUnit(chartResultPojo.getUnit());
        StatisticResultInfoDTO countStatisticResultInfoDto = new StatisticResultInfoDTO(throughDTO, tableFlag, yAxisIndex, hide, legend, unit, amountAggsPojo);

        List<StatisticResultInfoDTO> statisticResultInfoDtoList = Arrays.asList(amountStatisticResultInfoDto, countStatisticResultInfoDto);
        StatisticResultDTO statisticResultDto = new StatisticResultDTO(totalFlag, "", null, statisticResultInfoDtoList);
        chartResultDetailVO = systemChartHelp.packageResult(statisticResultDto, chartResultPojo, chartResultDetailVO);
        return chartResultDetailVO;
    }

    /**
     * 新增机会数/金额（简报看板）
     *
     * @param countFlag true处理新增数量，false处理新增金额
     * @param chartResultPojo
     * @return java.lang.String
     * @author 徐俊杰
     * @date 2019/3/12 19:45
     * @since v1.0
     */
    public String opportunityAddBoard(boolean countFlag, ChartResultPojo chartResultPojo) throws XbbException {
        //ES获取新增机会数
        ElasticAggsPojo elasticAggsPojo = opportunityAdd2ElasticAggsPojo(countFlag, chartResultPojo, ValueTypeEnum.VALUE_TYPE);
        if (countFlag) {
            return elasticAggsPojo.getTotal();
        } else {
            return ChartResultUtil.formatValue2String(false, ValueTypeEnum.VALUE_MONEY, chartResultPojo.isThousandSwitch(), elasticAggsPojo.getTotal(), NumFormatEnum.getByCode(chartResultPojo.getNumFormat()), chartResultPojo.getNumFormatFlag());
        }
    }

    /**
     * 客户机会数/金额
     *
     * @param countFlag true处理新增数量，false处理新增金额
     * @param panoramaPojo 全景参数pojo
     * @return java.lang.String
     * @author zcp
     * @date 2019/6/3 20:59
     * @since v1.0
     * @version v1.0
     */
    public String panoramaCustomerOpportunity(boolean countFlag, PanoramaPojo panoramaPojo) throws XbbException {
        //ES获取客户机会
        String dataAttr = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.CUSTOMER_NAME);
        //不排除特定阶段，限制负责人，且不限制时间
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.panoramaQuery4Team(panoramaPojo, dataAttr, panoramaPojo.getDataId(), IndexTypeEnum.IDX_SAAS_OPPORTUNITY_TEAM);
        if (countFlag) {
            ElasticAggsPojo elasticAggsPojo = esHelper.elasticAggs(IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, null);
            return elasticAggsPojo.getTotal();
        } else {
            /*--------得到机会金额--------*/
            String sumField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_AMOUNT);
            double value = systemChartHelp.getDouble4Total(IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, sumField);
            return ChartResultUtil.formatValue2Double(false, ValueTypeEnum.VALUE_MONEY, false, value, NumFormatEnum.DEFAULT, false);
        }
    }

    /**
     * 新增机会数/金额（首页）
     *
     * @param countFlag true处理新增数量，false处理新增金额
     * @param chartResultPojo 入参，包含公司id，员工id、时间和部门筛选信息
     * @param chartResultDetailVO 出参
     * @return java.lang.Object
     * @throws XbbException
     * @author 徐俊杰
     * @date 2019/3/15 11:30
     * @since v1.0
     */
    public Object opportunityAddHome(boolean countFlag, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        ElasticAggsPojo elasticAggsPojo = opportunityAdd2ElasticAggsPojo(countFlag, chartResultPojo, ValueTypeEnum.VALUE_TYPE);
        ThroughDTO throughDTO = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        //是否画入表格内 0否1是
        Integer tableFlag = 0;
        String legend = null;
        String unit = null;
        Boolean totalFlag = false;
        String valueTitle = null;
        return systemChartHelp.packageResult4One(elasticAggsPojo, chartResultPojo, chartResultDetailVO, throughDTO, tableFlag, legend, unit, totalFlag, valueTitle);
    }

    /**
     * 跟进机会数
     * @param chartResultPojo
     * @return java.lang.String
     * @author zcp
     * @date 2019/4/15 20:51
     * @since v1.0
     * @version v1.0
     */
    public String opportunityFollowBoard(ChartResultPojo chartResultPojo) throws XbbException {
        //ES获取跟进客户数
        ElasticAggsPojo elasticAggsPojo = opportunityFollow2ElasticAggsPojo(chartResultPojo, ValueTypeEnum.VALUE_TYPE);
        return elasticAggsPojo.getTotal();
    }

    /**
     * 新增机会关联客户
     * @param chartResultPojo
     * @return java.lang.String
     * @author zcp
     * @date 2019/4/15 21:11
     * @since v1.0
     * @version v1.0
     */
    public String opportunityCustomerBoard(ChartResultPojo chartResultPojo) throws XbbException {
        //ES获取跟进客户数
        ElasticAggsPojo elasticAggsPojo = opportunityCustomer2ElasticAggsPojo(chartResultPojo, ValueTypeEnum.VALUE_TYPE);
        return elasticAggsPojo.getTotal();
    }
    /**
     * 赢单机会数、金额
     * @param countFlag true处理数量，false处理金额
     * @param chartResultPojo
     * @return java.lang.String
     * @author zcp
     * @date 2019/4/15 21:33
     * @since v1.0
     * @version v1.0
     */
    public String opportunityWin(boolean countFlag, ChartResultPojo chartResultPojo) throws XbbException {
        ElasticAggsPojo elasticAggsPojo = opportunityWin2ElasticAggsPojo(countFlag, chartResultPojo, ValueTypeEnum.VALUE_TYPE);
        if (countFlag) {
            return elasticAggsPojo.getTotal();
        } else {
            return ChartResultUtil.formatValue2String(false, ValueTypeEnum.VALUE_MONEY, false, elasticAggsPojo.getTotal(), NumFormatEnum.getByCode(chartResultPojo.getNumFormat()), chartResultPojo.getNumFormatFlag());
        }
    }

    /**
     * 到期机会数（首页）
     *
     * @param countFlag true处理到期数量，false处理到期金额
     * @param chartResultPojo 入参，包含公司id，员工id、时间和部门筛选信息
     * @param chartResultDetailVO 出参
     * @return java.lang.Object
     * @throws XbbException
     * @author 徐俊杰
     * @date 2019/3/15 14:23
     * @since v1.0
     */
    public Object opportunityExpireHome(boolean countFlag, ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        ElasticAggsPojo elasticAggsPojo = opportunityExpire2ElasticAggsPojo(countFlag, chartResultPojo, ValueTypeEnum.VALUE_TYPE);
        ThroughDTO throughDTO = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        //是否画入表格内 0否1是
        Integer tableFlag = 0;
        String legend = null;
        String unit = null;
        Boolean totalFlag = false;
        String valueTitle = null;
        return systemChartHelp.packageResult4One(elasticAggsPojo, chartResultPojo, chartResultDetailVO, throughDTO, tableFlag, legend, unit, totalFlag, valueTitle);
    }

    /**
     * 赢单机会转化率
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object
     * @throws XbbException
     * @author zcp
     * @date 2019/2/27 20:49
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityWinRateChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        ValueTypeEnum valueTypeEnum = ValueTypeEnum.ARRAY_TYPE;
        ElasticAggsPojo winElasticAggsPojo = opportunityWin2ElasticAggsPojo(true, chartResultPojo, valueTypeEnum);
        ElasticAggsPojo estimateElasticAggsPojo = opportunityEstimate2ElasticAggsPojo(true, chartResultPojo, valueTypeEnum);

        int totalWinCount = StringUtil.toInt(winElasticAggsPojo.getTotal(), 0);
        int totalEstimateCount = StringUtil.toInt(estimateElasticAggsPojo.getTotal(), 0);
        Double totalRate = ChartResultUtil.getRate(totalWinCount, totalEstimateCount);

        //计算并封装：赢单率
        ElasticAggsPojo elasticAggsPojo4WinRate = systemChartHelp.getRateResult4Int(winElasticAggsPojo, estimateElasticAggsPojo);
        elasticAggsPojo4WinRate.setTotal(totalRate + "%");

        ThroughDTO throughDto = new ThroughDTO(chartResultPojo, SystemCodeEnum.OPPORTUNITY_WIN_COUNT);
        //是否画入表格内 0否1是
        Integer tableFlag = 1;
        //分别采用两个不同单位的y轴，默认为yAxisIndex: 0，当为1时，代表另外一个y坐标轴
        Integer yAxisIndex = 0;
        Integer hide = 0;
        String legend = I18nMessageUtil.getMessage(I18nStringConstant.WINS_NUMBER);
        String unit = systemChartHelp.getUnit(I18nMessageUtil.getMessage(CommonConstant.EACH));
        Boolean totalFlag = false;
        StatisticResultInfoDTO winStatisticResultInfoDto = new StatisticResultInfoDTO(throughDto, tableFlag, yAxisIndex, hide, legend, unit, winElasticAggsPojo);
        //置入预计机会code（穿透需要指定指标类型）
        ThroughDTO throughDtoEstimate = new ThroughDTO(chartResultPojo, SystemCodeEnum.OPPORTUNITY_ESTIMATE_CHART);
        legend = I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_NUMBER);
        StatisticResultInfoDTO estimateStatisticResultInfoDto = new StatisticResultInfoDTO(throughDtoEstimate, tableFlag, yAxisIndex, hide, legend, unit, estimateElasticAggsPojo);
        legend = I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_WINCONVERSION_RATE);
        yAxisIndex = 1;
        unit = systemChartHelp.getUnit("%");
        //转化率不穿透
        StatisticResultInfoDTO rateStatisticResultInfoDto = new StatisticResultInfoDTO(null, tableFlag, yAxisIndex, hide, legend, unit, elasticAggsPojo4WinRate);
        List<StatisticResultInfoDTO> statisticResultInfoDtoList = Arrays.asList(winStatisticResultInfoDto, estimateStatisticResultInfoDto, rateStatisticResultInfoDto);
        StatisticResultDTO statisticResultDto = new StatisticResultDTO(totalFlag, "", null, statisticResultInfoDtoList);
        chartResultDetailVO = systemChartHelp.packageResult(statisticResultDto, chartResultPojo, chartResultDetailVO);
        return chartResultDetailVO;
    }
    /**
     * 销售机会区域分布
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object
     * @author zcp
     * @date 2019/2/27 22:33
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityMapChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        /*--------处理查询条件--------*/
        BoolQueryBuilder boolQueryBuilder = boolQuery();
        boolQueryBuilder.filter(termQuery("corpid.keyword", chartResultPojo.getCorpid()));
        //从机会中获取出关联的客户id
        getDealCustomerIdfromOpportunity(chartResultPojo, boolQueryBuilder);
        //地图
        List<ResultDataSeriesChartVO> list = systemChartHelp.getCustomerDrawRegionMap(chartResultPojo, boolQueryBuilder);
        //地图不穿透
        ThroughDTO throughDto = null;
        StatisticResultPieInfoDTO statisticResultPieInfoDto = new StatisticResultPieInfoDTO("销售机会区域分布", list, throughDto);
        List<StatisticResultPieInfoDTO> pieInfoDtoList = Collections.singletonList(statisticResultPieInfoDto);
        return systemChartHelp.packageResult4Map("机会数量", pieInfoDtoList, chartResultPojo, chartResultDetailVO);
    }
    /**
     * 输单原因
     * @param chartResultPojo
     * @param chartResultDetailVO
     * @return java.lang.Object
     * @author zcp
     * @date 2019/2/28 10:06
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityLoseCauseChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        String corpid = chartResultPojo.getCorpid();
        String field = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_END_TIME);
        //阶段：0其他普通类型，1赢单，2输单，3取消，只统计输单
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        Set<String> saleStageCodeIn = stageFindStrategy.findStageCodeList(corpid, new StageFindPojo(chartResultPojo.getRefId(), StageTypeEnum.getLoseType()));
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, field, true, saleStageCodeIn, false);
        //输单原因
        String fieldGroup = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.WASTAGE);
       /* //查询出机会的表单id
        PaasFormEntityExt paasFormEntityExt = paasFormModel.getByBusinessType(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode(), corpid);
        Long formId = paasFormEntityExt.getId();*/
        //输单原因字典
        List<ItemPoJo> loseCauseList = dataDictionaryModel.getArrayList(DictionaryEnum.SALE_STAGE_WASTAGE, corpid, chartResultPojo.getRefId());

        List<ResultDataSeriesChartVO> list = systemChartHelp.getDataDictionaryDataSeries(IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, fieldGroup, loseCauseList);
        String tableName = I18nMessageUtil.getMessage(ChartI18Constant.REASON_FOR_LOSING);

        ThroughDTO throughDto = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        StatisticResultPieInfoDTO statisticResultPieInfoDto = new StatisticResultPieInfoDTO(tableName, list, throughDto);
        List<StatisticResultPieInfoDTO> pieInfoDtoList = Collections.singletonList(statisticResultPieInfoDto);
        return systemChartHelp.packageResult4Pie(tableName, pieInfoDtoList, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 阶段变更分析
     * @param chartResultPojo 入参，包含公司id，员工id、时间和部门筛选信息
     * @param chartResultDetailVO 出参
     * @return java.lang.Object
     * @throws XbbException
     * @author zcp
     * @since v1.0
     * @version v1.0
     */
    public Object opportunityStageChart(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO) throws XbbException {
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        //只有版本筛选
        List<? extends PaasStageEntity> saleStageList = stageFindStrategy.findStageListSpecial(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), chartResultPojo.getArgTwoId()));
        //全部阶段code集合
        Set<String> allStageCodeIn = new HashSet<>(saleStageList.size());
        for (PaasStageEntity stage : saleStageList) {
            allStageCodeIn.add(stage.getCode());
        }
        String field = StringConstant.ADD_TIME;
        String termsField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.SALE_STAGE);
        String sumField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_AMOUNT);
        //获取特定的机会阶段（机会formId）
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, field, true, allStageCodeIn, false);
        AggMorePojo aggMorePojo = new AggMorePojo(field, termsField, allStageCodeIn.size(), sumField);
        //得到并封装统计结果 - 只按照最内层的时间分桶塞入n个map，每个map内封装数量、总金额的显示信息
        List<Map<String, StageFunnelInfoPojo>> list = systemChartHelp.getAgg4TimeAndField(chartResultPojo, ValueTypeEnum.ARRAY_TYPE, IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, aggMorePojo);
        //格式化返回结果
        return systemChartHelp.packageResult4StageTable(saleStageList, list, chartResultPojo, chartResultDetailVO);
    }

    /**
     * 新增机会数pk
     * @param userEntity 登录员工信息
     * @param chartResultPkPojo pk参数
     * @param chartResultPkDetailVO 塞入pk结果
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultPkDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/3/19 21:41
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkRuleOpportunityAddCount(UserEntity userEntity, ChartEntity chartEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_OPPORTUNITY;
        BoolQueryBuilder boolQueryBuilder = chartRuleQueryHelp.opportunityQuery4PkAdd(chartResultPkPojo);
        if (chartDtoHelp.checkJoinRuleIfAll(chartEntity, chartResultPkPojo)) {
            //表本身的createId不能变更，因此在data字典内增加创建人key
            boolQueryBuilder.filter(termsQuery("creatorId.keyword", chartResultPkPojo.getUserIdIn()));
        }
        // chartRuleQueryHelp.formOpenBase(boolQueryBuilder, chartResultPkPojo, XbbRefTypeEnum.SALES_OPPORTUNITY);
        //以用户userId分组
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("aggs01").field("creatorId.keyword");
        termsAggregationBuilder.executionHint(ElasticConstant.EXECUTION_HINT);
        int pkType = chartResultPkPojo.getPkType();
        return pkRuleHelp.getPkResult4Count(userEntity, chartEntity, chartResultPkPojo, chartResultPkDetailVO, indexTypeEnum, boolQueryBuilder, termsAggregationBuilder, pkType);
    }
    /**
     * 新增机会金额pk
     * @param userEntity 登录员工信息
     * @param chartResultPkPojo pk参数
     * @param chartResultPkDetailVO 塞入pk结果
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultPkDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/3/21 10:16
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkRuleOpportunityAddAmount(UserEntity userEntity, ChartEntity chartEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_OPPORTUNITY;
        BoolQueryBuilder boolQueryBuilder = chartRuleQueryHelp.opportunityQuery4PkAdd(chartResultPkPojo);
        // chartRuleQueryHelp.formOpenBase(boolQueryBuilder, chartResultPkPojo, XbbRefTypeEnum.SALES_OPPORTUNITY);
        String fieldGroup = "creatorId.keyword";
        if (chartDtoHelp.checkJoinRuleIfAll(chartEntity, chartResultPkPojo)) {
            //表本身的createId不能变更，因此在data字典内增加创建人key
            boolQueryBuilder.filter(termsQuery("creatorId.keyword", chartResultPkPojo.getUserIdIn()));
        }
        String sumField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_AMOUNT);
        int pkType = chartResultPkPojo.getPkType();
        chartResultPkPojo.setValueTypeEnum(ValueTypeEnum.VALUE_MONEY);
        return pkRuleHelp.getPkResult4Double(userEntity, chartEntity, chartResultPkPojo, chartResultPkDetailVO, indexTypeEnum, boolQueryBuilder, fieldGroup, sumField, pkType);
    }

    /**
     * 新增机会数pk
     * @param userEntity 登录员工信息
     * @param chartResultPkPojo pk参数
     * @param chartResultPkDetailVO 塞入pk结果
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultPkDetailVO
     * @throws XbbException
     * @author zcp
     * @date 2019/3/19 21:41
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkOpportunityAddCount(UserEntity userEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_OPPORTUNITY;
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4PkAdd(chartResultPkPojo);
        //以用户userId分组
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("aggs01").field("creatorId.keyword");
        termsAggregationBuilder.executionHint(ElasticConstant.EXECUTION_HINT);
        int pkType = chartResultPkPojo.getPkType();
        return pkHelp.getPkResult4Count(userEntity, chartResultPkPojo, chartResultPkDetailVO, indexTypeEnum, boolQueryBuilder, termsAggregationBuilder, pkType);
    }
    /**
     * 新增机会金额pk
     * @param userEntity 登录员工信息
     * @param chartResultPkPojo pk参数
     * @param chartResultPkDetailVO 塞入pk结果
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultPkDetailVO 
     * @throws XbbException
     * @author zcp
     * @date 2019/3/21 10:16
     * @since v1.0
     * @version v1.0
     */
    public ChartResultPkDetailVO pkOpportunityAddAmount(UserEntity userEntity, ChartResultPkPojo chartResultPkPojo, ChartResultPkDetailVO chartResultPkDetailVO) throws XbbException {
        IndexTypeEnum indexTypeEnum = IndexTypeEnum.IDX_SAAS_OPPORTUNITY;
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4PkAdd(chartResultPkPojo);
        String fieldGroup = "creatorId.keyword";
        String sumField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_AMOUNT);
        int pkType = chartResultPkPojo.getPkType();
        chartResultPkPojo.setValueTypeEnum(ValueTypeEnum.VALUE_MONEY);
        return pkHelp.getPkResult4Double(userEntity, chartResultPkPojo, chartResultPkDetailVO, indexTypeEnum, boolQueryBuilder, fieldGroup, sumField, pkType);
    }

    //-------------------private方法-------------------
    /**
     * 从机会中获取关联的客户id
     * @param chartResultPojo
     * @param boolQueryBuilder
     * @author zcp
     * @date 2019/2/27 22:14
     * @since v1.0
     * @version v1.0
     */
    private void getDealCustomerIdfromOpportunity(ChartResultPojo chartResultPojo, BoolQueryBuilder boolQueryBuilder) throws XbbException {
        //字段
        String fieldTime = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_END_TIME);
        //阶段：0其他普通类型，1赢单，2输单，3取消，不统计输单、取消
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        Set<String> saleStageCodeIn = stageFindStrategy.findStageCodeList(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), StageTypeEnum.getExceptLoseCancelType()));
        /*--------处理查询条件，限制开始时间--------*/
        BoolQueryBuilder opportunityQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, fieldTime, true, saleStageCodeIn, true);
        //分组字段---客户id
        String fieldGroup = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.CUSTOMER_NAME);
        FloorAggsKeyPojo floorAggsKeyPojo = systemChartHelp.getFloorAggsKey(chartResultPojo, IndexTypeEnum.IDX_SAAS_OPPORTUNITY, opportunityQueryBuilder, fieldTime, fieldGroup, true);
        Set totalSet = floorAggsKeyPojo.getTotalSet();
        if (totalSet.size() == 0) {
            totalSet.add(-1);
        }
        boolQueryBuilder.filter(termsQuery("dataId", totalSet));
    }

    /**
     * 获取新增机会数、或新增机会金额的ElasticAggsPojo
     * @param countFlag true处理新增数量，false处理新增金额
     * @param chartResultPojo 参数pojo
     * @param valueTypeEnum 返回值类型
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/27 11:37
     * @version v1.0
     * @since v1.0
     */
    private ElasticAggsPojo opportunityAdd2ElasticAggsPojo(boolean countFlag, ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum) throws XbbException {
        String field = "addTime";
        //阶段：0其他普通类型，1赢单，2输单，3取消，不统计取消
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        //允许版本筛选（首页机会模块）、阶段筛选（阶段筛选器）
        Set<String> statisticsCodeIn = stageFindStrategy.findStageCodeList(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), chartResultPojo.getArgTwoId(), StageTypeEnum.getExceptCancelType(), chartResultPojo.getArgStrIn()));
        return getElasticAggsPojo4Opp(countFlag, chartResultPojo, valueTypeEnum, field, statisticsCodeIn, true);
    }
    /**
     * 获取预计机会数的ElasticAggsPojo
     * @param countFlag true处理预计数量，false处理预计金额
     * @param chartResultPojo
     * @param valueTypeEnum
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/27 19:43
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo opportunityEstimate2ElasticAggsPojo(boolean countFlag, ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum) throws XbbException {
        String field = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_END_TIME);
        //阶段：0其他普通类型，1赢单，2输单，3取消，不统计输单、取消
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        //允许版本筛选（和赢单在一起）、阶段筛选（阶段筛选器）
        Set<String> saleStageCodeIn = stageFindStrategy.findStageCodeList(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), chartResultPojo.getArgTwoId(), StageTypeEnum.getExceptLoseCancelType(), chartResultPojo.getArgStrIn()));
        return getElasticAggsPojo4Opp(countFlag, chartResultPojo, valueTypeEnum, field, saleStageCodeIn, true);
    }
    /**
     * 获取预计机会数的ElasticAggsPojo
     * @param countFlag true处理赢单数量，false处理赢单金额
     * @param chartResultPojo 参数pojo
     * @param valueTypeEnum 返回值类型
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/27 19:56
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo opportunityWin2ElasticAggsPojo(boolean countFlag, ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum) throws XbbException {
        String field = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_END_TIME);
        //阶段：0其他普通类型，1赢单，2输单，3取消，只统计赢单
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        Set<String> winStatisticsCodeIn = stageFindStrategy.findStageCodeList(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), chartResultPojo.getArgTwoId(), StageTypeEnum.getSuccessType()));
        return getElasticAggsPojo4Opp(countFlag, chartResultPojo, valueTypeEnum, field, winStatisticsCodeIn, false);
    }

    /**
     * 获取到期机会的ElasticAggsPojo
     *
     * @param countFlag true处理到期数量，false处理到期金额
     * @param chartResultPojo 参数pojo
     * @param valueTypeEnum 返回值类型
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author 徐俊杰
     * @date 2019/3/15 14:16
     * @since v1.0
     */
    private ElasticAggsPojo opportunityExpire2ElasticAggsPojo(boolean countFlag, ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum) throws XbbException {
        String field = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_END_TIME);
        //阶段：0其他普通类型，1赢单，2输单，3取消，只统计普通类型
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        Set<String> saleStageCodeIn = stageFindStrategy.findStageCodeList(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), chartResultPojo.getArgTwoId(), StageTypeEnum.getOrdinaryType()));
        return getElasticAggsPojo4Opp(countFlag, chartResultPojo, valueTypeEnum, field, saleStageCodeIn, true);
    }

    /**
     * 跟进机会
     * @param chartResultPojo
     * @param valueTypeEnum
     * @return com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo 
     * @author zcp
     * @date 2019/4/15 20:25
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo opportunityFollow2ElasticAggsPojo(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum) throws XbbException {
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.communicateQuery4UserLimitStart(chartResultPojo);
        boolQueryBuilder = chartQueryHelp.communicateFollowQuery(boolQueryBuilder, XbbRefTypeEnum.SALES_OPPORTUNITY);
        String fieldTime = CustomerCommunicateEnum.getEsAttr4Keyword(CustomerCommunicateEnum.COMMUNICATE_TIME);
        //排重字段---机会id
        String fieldGroup = CustomerCommunicateEnum.getEsAttr4Keyword(CustomerCommunicateEnum.COMMUNICATE_BUSINESS);
        //用关联机会id聚合结果
        return systemChartHelp.distinctAggregations(chartResultPojo, valueTypeEnum, IndexTypeEnum.IDX_SAAS_CUSTOMER_COMMUNICATE, boolQueryBuilder, fieldTime, fieldGroup);
    }

    /**
     * 新增机会关联客户
     * @param chartResultPojo
     * @param valueTypeEnum
     * @return com.xbongbong.pro.statistic.result.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/4/15 21:11
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo opportunityCustomer2ElasticAggsPojo(ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum) throws XbbException {
        String fieldTime = "addTime";
        //阶段：0其他普通类型，1赢单，2输单，3取消，不统计取消
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        Set<String> statisticsCodeIn = stageFindStrategy.findStageCodeList(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), StageTypeEnum.getExceptCancelType()));
        /*--------处理查询条件--------*/
        //获取特定的机会阶段（机会formId）
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, fieldTime, true, statisticsCodeIn, true);

        //排重字段---客户id
        String fieldGroup = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.CUSTOMER_NAME);
        //用关联客户id聚合结果
        return systemChartHelp.distinctAggregations(chartResultPojo, valueTypeEnum, IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, fieldTime, fieldGroup);
    }

    /**
     * 获取机会数量、或金额相关统计结果
     * @param countFlag true处理数量，false处理金额
     * @param chartResultPojo 参数pojo
     * @param valueTypeEnum 返回值类型
     * @param field 时间字段（新增是创建时间，预计是预计结束日期）
     * @param noStatisticsCodeIn 阶段：0其他普通类型，1赢单，2输单，3取消，新增、预计机会等不同的机会统计，所需要获取的阶段不一样
     * @param isMustNotQuery
     * @return com.xbongbong.paas.elasticsearch.pojo.ElasticAggsPojo
     * @author zcp
     * @date 2019/2/27 19:06
     * @since v1.0
     * @version v1.0
     */
    private ElasticAggsPojo getElasticAggsPojo4Opp(boolean countFlag, ChartResultPojo chartResultPojo, ValueTypeEnum valueTypeEnum, String field, Set<String> noStatisticsCodeIn, boolean isMustNotQuery) throws XbbException {
        /*--------处理查询条件--------*/
        //获取特定的机会阶段（机会formId）
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, field, true, noStatisticsCodeIn, isMustNotQuery);
        if (countFlag) {
            /*--------得到机会数量--------*/
            return systemChartHelp.getCount4Agg(chartResultPojo, valueTypeEnum, IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, field);
        }else {
            chartResultPojo.setValueTypeEnum(ValueTypeEnum.VALUE_MONEY);
            /*--------得到机会金额--------*/
            String sumField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_AMOUNT);
            /*
             2019/4/6 不再按照多币种聚合求值 getAmount4Agg(chartResultPojo, valueTypeEnum, IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, field, currencyField, sumField, currencyRateMap);
             */
            return systemChartHelp.getDouble4Agg(chartResultPojo, valueTypeEnum, IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, field, sumField);
        }
    }

    /**
     * 按阶段聚合，再按照金额sum，得到最终各个阶段的机会数、机会金额
     * @param chartResultPojo 参数pojo
     * @return com.xbongbong.pro.statistic.result.pojo.StageFunnelPojo
     * @author zcp
     * @date 2019/4/6 17:43
     * @since v1.0
     * @version v1.0
     */
    private StageFunnelPojo getFunnel4Opportunity(ChartResultPojo chartResultPojo) throws XbbException {
        StageFindStrategy stageFindStrategy = StageFindFactory.getStageFindStrategy(XbbRefTypeEnum.SALES_OPPORTUNITY.getCode());
        //（秦春阳）2020/8/21 之后需求： 漏斗增加了“输单”，“取消”两个阶段，所以默认要查出所有机会，但是计算总数、赢率等又要剔除这两个状态，因此默认查出所有启用的阶段
        List<? extends PaasStageEntity> saleStageList = stageFindStrategy.findStageListSpecial(chartResultPojo.getCorpid(), new StageFindPojo(chartResultPojo.getRefId(), chartResultPojo.getArgTwoId()));
        //全部阶段code集合
        List<String> allStageCodeIn = new ArrayList<>();
        //输单、取消类型code集合（不参与计算总数）
        List<String> notStatisticsCodeIn = new ArrayList<>();
        //普通阶段code集合，结束阶段（成功、失败、取消）不用计算停留时长
        List<String> ordinaryCodeIn = new ArrayList<>();
        List<Integer> loseCancelTypeList = StageTypeEnum.getLoseCancelType();
        List<Integer> ordinaryTypeList = StageTypeEnum.getOrdinaryType();
        for (PaasStageEntity stage : saleStageList) {
            String code = stage.getCode();
            Integer type = stage.getType();
            allStageCodeIn.add(code);
            if (loseCancelTypeList.contains(type)) {
                notStatisticsCodeIn.add(code);
            } else if (ordinaryTypeList.contains(type)) {
                ordinaryCodeIn.add(code);
            }
        }
        //---------------按阶段，再按照金额sum
//        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, fieldTime, true, allStageCodeIn, false);
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQueryBaseForSaleFunnel(chartResultPojo, true, allStageCodeIn);
        //两层聚合（其中第二层处理三个聚合）
        String termsField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.SALE_STAGE);
        String sumField = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.ESTIMATE_AMOUNT);
        String sumField2 = SalesOpportunityEnum.getEsAttr4Keyword(SalesOpportunityEnum.STAGE_RATIO);
        //金额*比例
        Script script = ScriptHelp.scriptMultiply(SalesOpportunityEnum.getAttrConnectData(SalesOpportunityEnum.ESTIMATE_AMOUNT), SalesOpportunityEnum.getAttrConnectData(SalesOpportunityEnum.STAGE_RATIO));
        boolQueryBuilder.filter(existsQuery(SalesOpportunityEnum.getAttrConnectData(SalesOpportunityEnum.ESTIMATE_AMOUNT)));
        boolQueryBuilder.filter(existsQuery(SalesOpportunityEnum.getAttrConnectData(SalesOpportunityEnum.STAGE_RATIO)));
        //得到聚合条件（第一层阶段code，第二层同时对两个字段求和、并sum(两个字段相乘)）
        AggSubFactoriesPojo aggSubFactoriesPojo = esHelper.getTermsAggregation4ThreeSumAndMcl(allStageCodeIn.size(), termsField, sumField, sumField2, script);
        TermsAggregationBuilder termsAggregationBuilder = aggSubFactoriesPojo.getTermsAggregationBuilder();
        String aggsSub1 = aggSubFactoriesPojo.getAggsSub1();
        String aggsSub2 = aggSubFactoriesPojo.getAggsSub2();
        String aggsSub3 = aggSubFactoriesPojo.getAggsSub3();

        JSONObject resultJsonObject = esHelper.elasticAggs4Buckets2Object(IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, termsAggregationBuilder);
        JSONArray bucketsArray = resultJsonObject.getJSONArray("buckets");
        //总数（注意：这里总数指的是 "除去输单和取消两个阶段之后，其他阶段的机会数总和"）
        //这个total的用处是用在表格计算“阶段转化率”的,  不要把"输单","取消"的算进去。阶段转化率 = 该阶段后面阶段的机会数 / (该阶段机会数+该阶段后面的阶段机会总数）
        int total = 0;
        /**
         * 阶段id--该阶段对应的相关信息pojo
         */
        Map<String, StageFunnelInfoPojo> stageIdInfoMap = new HashMap<>(bucketsArray.size());
        String valueStr = BasicConstant.VALUE;
        for (Object object : bucketsArray) {
            if(object!=null){
                JSONObject json = (JSONObject) object;
                String code = json.getString("key");
                Integer count = json.getInteger("doc_count");
                //输单,取消阶段机会的不计入机会总数，因为后面计算转化率不用
                if(!notStatisticsCodeIn.contains(code)){
                    total += count;
                }
                //第二层第1个聚合桶结果-机会金额
                Double amount = json.getJSONObject(aggsSub1).getDoubleValue(valueStr);
                //第二层第2个聚合桶结果-比例
                Double rateTotal = json.getJSONObject(aggsSub2).getDoubleValue(valueStr);
                Double rateAvg = Arith.div(rateTotal, count);
                //第二层第3个聚合桶结果-比例
                Double amountEstimate = json.getJSONObject(aggsSub3).getDoubleValue(valueStr);
                //实体data内存的比例是乘过100的值，因此，计算预计时需要除以100
                amountEstimate = Arith.div(amountEstimate, 100);
                StageFunnelInfoPojo stageFunnelInfoPojo = new StageFunnelInfoPojo(count, amount, rateTotal, rateAvg, amountEstimate);
                stageIdInfoMap.put(code, stageFunnelInfoPojo);
            }
        }
        //只把非结束阶段的code放入集合
        StageFunnelPojo stageFunnelPojo = new StageFunnelPojo(total, stageIdInfoMap, saleStageList, ordinaryCodeIn);
        return stageFunnelPojo;
    }
    /**
     * 封装普通销售漏斗，只包含名称、金额
     * @param chartResultPojo 请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @param saleStageList 阶段list
     * @param stageFunnelPojo 漏斗vo，封装各个阶段的信息
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @date 2019/3/10 18:00
     * @since v1.0
     * @version v1.0
     */
    private ChartResultDetailVO packageFunnel(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, List<? extends PaasStageEntity> saleStageList, StageFunnelPojo stageFunnelPojo){
        //阶段id--该阶段对应的相关信息pojo
        Map<String, StageFunnelInfoPojo> stageIdInfoMap = stageFunnelPojo.getStageIdInfoMap();
        //声明图和表的信息VO
        List<ResultChartVO> resultChartVOList = new ArrayList<>();
        ResultChartVO resultChartVO = new ResultChartVO();
        List<ResultDataSeriesChartVO> dataSeriesList = new ArrayList<>();
        ResultTableVO resultTableVO = new ResultTableVO();
        Integer numFormat = chartResultPojo.getNumFormat();
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(numFormat);
        String unit = numFormatEnum.getUnit();
        List<String> title = Arrays.asList("机会阶段", "数量/个", "金额/" + unit);
        resultTableVO.setTitle(systemChartHelp.titleNumRight(title));
        List<List<ResultDataTableVO>> resultDataTableVOList = new ArrayList<>();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        if (chartEntity != null) {
            //如果图信息存在，则把图相关信息置入chartResultChartVO
            BeanUtil.copyProperties(chartEntity, resultChartVO);
        }
        int size = saleStageList.size();
        Integer average = (size == 0) ? 0 : 100 / size;
        Integer flagValue = 0;
        //处理穿透
        ThroughDTO throughDto = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        TimePojo timePojo = throughHelp.formatThroughTimeInfo(false, chartResultPojo.getFiscalYearTimePojo(), chartResultPojo.getStartTime(), chartResultPojo.getEndTime());
        boolean isLostOrderButtonStart = true;
        boolean isCancelButtonStart = true;
        boolean isLoss;
        boolean isCancel;
        JSONArray buttonJsonArray = chartEntity.getButtonAttr();
        if (Objects.isNull(buttonJsonArray)){
            buttonJsonArray = new JSONArray();
        }
        if (CollectionUtils.isNotEmpty(buttonJsonArray)) {
            for (int i = 0; i < buttonJsonArray.size(); i++) {
                JSONObject jsonObject = buttonJsonArray.getJSONObject(i);
                String config = jsonObject.getString("config");
                String enable = jsonObject.getString("configValue");
                ChartConfigEnum chartConfigEnum = ChartConfigEnum.getByConfig(config);
                switch (chartConfigEnum) {
                    case FUNNEL_OPPORTUNITY_LOST_ORDER_BUTTON:
                        isLostOrderButtonStart = Objects.equals(enable, StringConstant.POSITIVE_NUMBER);
                        break;
                    case FUNNEL_OPPORTUNITY_CANCEL_BUTTON:
                        isCancelButtonStart = Objects.equals(enable, StringConstant.POSITIVE_NUMBER);
                        break;
                    default:
                        break;
                }
            }
        }

        for (int i = 0; i < size; i++) {
            PaasStageEntity saleStageEntity = saleStageList.get(i);
            String realName = saleStageEntity.getName();
            String code = saleStageEntity.getCode();
            isLoss = Objects.equals(StageTypeEnum.FAIL_STAGE.getType(), saleStageEntity.getType());
            isCancel = Objects.equals(StageTypeEnum.CANCEL_STAGE.getType(), saleStageEntity.getType());
            if (  (!isLostOrderButtonStart && isLoss) || (!isCancelButtonStart && isCancel) )  {
                continue;
            }
            StageFunnelInfoPojo stageFunnelInfoPojo = stageIdInfoMap.get(code);
            int count = (stageFunnelInfoPojo == null)? 0: stageFunnelInfoPojo.getCount();
            Double amount = (stageFunnelInfoPojo == null) ? 0D : stageFunnelInfoPojo.getAmount();
            String amountStr = ChartResultUtil.formatVal2Thousands(BigDecimal.valueOf(amount), numFormatEnum);
            String name = realName + String.format(I18nMessageUtil.getMessage(ChartI18Constant.SALE_OPPORTUNITY_STATISTICS_BOARD),count, amountStr + unit);
            int value;
            if (i == 0) {
                value = 100;
            } else if (i < size) {
                value = 100 - average * i;
            } else {
                value = 100 - flagValue;
            }
            flagValue = value;
            //置入穿透需要的阶段code
            throughDto.setDictionaryCodeThrough(String.valueOf(code));
            String through = throughHelp.getThrough(throughDto, timePojo);
            ResultDataSeriesChartVO dataSeries = new ResultDataSeriesChartVO(name, realName, String.valueOf(code), String.valueOf(value));
            dataSeries.setThrough(through);
            dataSeriesList.add(dataSeries);
            ResultDataTableVO nameVo = new ResultDataTableVO(realName, null);
            ResultDataTableVO countVo = new ResultDataTableVO(String.valueOf(count), through);
            ResultDataTableVO amountVo = new ResultDataTableVO(amountStr, through);
            List<ResultDataTableVO> resultDataTableVOs = Arrays.asList(nameVo, countVo, amountVo);
            resultDataTableVOList.add(resultDataTableVOs);
        }
        resultChartVO.setDataSeries(dataSeriesList);
        resultChartVOList.add(resultChartVO);
        resultTableVO.setData(resultDataTableVOList);
        chartResultDetailVO.setChart(resultChartVOList);
        chartResultDetailVO.setTable(resultTableVO);
        return chartResultDetailVO;
    }
    /**
     * 封装漏斗报表中心销售漏斗所需数据
     * 包含：阶段转化率、平均停留时长等
     * @param chartResultPojo 请求系统指标接口需要用到的pojo类（内部存系统指标统计时需要的参数）
     * @param chartResultDetailVO 统计需要渲染的图（表---可能同时存在，可能某一个存在）塞入该vo
     * @param saleStageList 阶段list
     * @param stageFunnelPojo 漏斗vo，封装各个阶段的信息，特殊处理："输单"，"取消"阶段只展示"销售机会阶段"名称,"机会数量","机会金额"，
     * @param dayMap 阶段code--平均停留时长
     * @return com.xbongbong.pro.statistic.pojo.vo.ChartResultDetailVO
     * @author zcp
     * @date 2019/3/8 20:40
     * @since v1.0
     * @version v1.0
     */
    private ChartResultDetailVO packageFunnelFull(ChartResultPojo chartResultPojo, ChartResultDetailVO chartResultDetailVO, List<? extends PaasStageEntity> saleStageList, StageFunnelPojo stageFunnelPojo, Map<String, Double> dayMap){
        Integer numFormat = chartResultPojo.getNumFormat();
        NumFormatEnum numFormatEnum = NumFormatEnum.getByCode(numFormat);
        String unit = chartResultPojo.getUnit();
        //阶段id--该阶段对应的相关信息pojo
        Map<String, StageFunnelInfoPojo> stageIdInfoMap = stageFunnelPojo.getStageIdInfoMap();
        //声明图和表的信息VO
        List<ResultChartVO> resultChartVOList = new ArrayList<>();
        ResultChartVO resultChartVO = new ResultChartVO();
        List<ResultDataSeriesChartVO> dataSeriesList = new ArrayList<>();
        ResultTableVO resultTableVO = new ResultTableVO();
        List<String> title = Arrays.asList(String.format(I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_FUNNEL_TITLE), unit, unit).split(","));
        resultTableVO.setTitle(systemChartHelp.titleNumRight(title));
        List<List<ResultDataTableVO>> resultDataTableVOList = new ArrayList<>();
        ChartEntity chartEntity = chartResultPojo.getChartEntity();
        if (chartEntity != null) {
            BeanUtil.copyProperties(chartEntity, resultChartVO);
        }
        int size = saleStageList.size();
        int average = (size == 0) ? 0 : 100 / size;
        int flagValue = 0;
        //该阶段后面所有阶段的总数（除去该阶段）
        int nextCount = stageFunnelPojo.getTotal();
        //处理穿透
        ThroughDTO throughDto = new ThroughDTO(chartResultPojo, chartResultPojo.getSystemCodeEnum());
        TimePojo timePojo = throughHelp.formatThroughTimeInfo(false, chartResultPojo.getFiscalYearTimePojo(), chartResultPojo.getStartTime(), chartResultPojo.getEndTime());
        Integer totalCount = BasicConstant.ZERO;
        BigDecimal totalAmount=new BigDecimal("0.00");
        BigDecimal totalPredictionAmount=new BigDecimal("0.00");
        boolean isLoss;
        boolean isCancel;
        boolean isLostOrderButtonStart = true;
        boolean isCancelButtonStart = true;
        JSONArray buttonJsonArray = chartEntity.getButtonAttr();
        if (Objects.isNull(buttonJsonArray)){
            buttonJsonArray = new JSONArray();
        }
        if (CollectionUtils.isNotEmpty(buttonJsonArray)) {
            for (int i = 0; i < buttonJsonArray.size(); i++) {
                JSONObject jsonObject = buttonJsonArray.getJSONObject(i);
                String config = jsonObject.getString("config");
                String enable = jsonObject.getString("configValue");
                ChartConfigEnum chartConfigEnum = ChartConfigEnum.getByConfig(config);
                switch (chartConfigEnum) {
                    case FUNNEL_OPPORTUNITY_LOST_ORDER_BUTTON:
                        isLostOrderButtonStart = Objects.equals(enable, StringConstant.POSITIVE_NUMBER);
                        break;
                    case FUNNEL_OPPORTUNITY_CANCEL_BUTTON:
                        isCancelButtonStart = Objects.equals(enable, StringConstant.POSITIVE_NUMBER);
                        break;
                    default:
                        break;
                }
            }
        }

        List<Integer> loseCancelTypeList = StageTypeEnum.getLoseCancelType();
        List<Integer> successTypeList = StageTypeEnum.getSuccessType();
        for (int i = 0; i < size; i++) {
            PaasStageEntity saleStageEntity = saleStageList.get(i);
            String realName = saleStageEntity.getName();
            String code = saleStageEntity.getCode();
            Integer type = saleStageEntity.getType();
            isLoss = Objects.equals(StageTypeEnum.FAIL_STAGE.getType(), type);
            isCancel = Objects.equals(StageTypeEnum.CANCEL_STAGE.getType(), type);
            if (  (!isLostOrderButtonStart && isLoss) || (!isCancelButtonStart && isCancel) )  {
                continue;
            }
            StageFunnelInfoPojo stageFunnelInfoPojo = stageIdInfoMap.getOrDefault(code, new StageFunnelInfoPojo());
            int count = (stageFunnelInfoPojo == null)? 0: stageFunnelInfoPojo.getCount();
            Double amount = (stageFunnelInfoPojo == null) ? 0D : stageFunnelInfoPojo.getAmount();
            String amountStr = String.valueOf(ChartResultUtil.formatVal4Num(BigDecimal.valueOf(amount), numFormatEnum));
            //特殊逻辑 “输单”和“取消”的两行，只需要展示“机会数量”，“机会金额”的数据，其他字段展示使用"-"，并且计入总计。
            if(loseCancelTypeList.contains(type)){
                //图数据:
                //获取穿透through（根据code）
                throughDto.setDictionaryCodeThrough(code);
                String through = throughHelp.getThrough(throughDto, timePojo);
                //文案格式eg：输单（1个，共120元）
                String name = realName + String.format(I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_STAGE_CONTENT_4), count, amountStr, unit);
                int value = 100 - average * i;
                ResultDataSeriesChartVO dataSeries = new ResultDataSeriesChartVO(name, realName, code, String.valueOf(value));
                dataSeries.setThrough(through);
                dataSeriesList.add(dataSeries);

                //table表数据：字段 (1)nameVo:销售阶段名称 （2）countVo:机会数量 (3)amountVo:机会金额(4)其他字段:"-"展示
                ResultDataTableVO nameVo = new ResultDataTableVO(realName, null);
                ResultDataTableVO countVo = new ResultDataTableVO(String.valueOf(count), through);
                ResultDataTableVO amountVo = new ResultDataTableVO(amountStr, through);
                ResultDataTableVO defaultVo = new ResultDataTableVO("-", null);
                List<ResultDataTableVO> resultDataTableVOs = Arrays.asList(nameVo, countVo, amountVo, defaultVo, defaultVo, defaultVo, defaultVo);
                resultDataTableVOList.add(resultDataTableVOs);
                //参与总计（机会数量，机会金额,）
                totalCount += count;
                totalAmount = totalAmount.add(new BigDecimal(amount));
                continue;
            }
            //平均预测赢率
            Double estimateWinRate = stageFunnelInfoPojo.getRateAvg();

            DecimalFormat decimalFormat = new DecimalFormat("###################.##");
            String winRate = (estimateWinRate == null || estimateWinRate == 0) ? "0%" : decimalFormat.format(estimateWinRate) + "%";
            double predictionAmount = stageFunnelInfoPojo.getAmountEstimate();
            String predictionAmountStr = StringUtil.formatDouble(ChartResultUtil.formatVal4Num(BigDecimal.valueOf(predictionAmount), numFormatEnum), "#,##0.00");
             /**
             处理阶段转化率（最后一个赢单阶段无需计算），以及阶段停留时长：除了该阶段以及该阶段前面的数，即该阶段后面所有阶段的总数
             最后一个赢单阶段无需计算转化率和停留时长
             */
            nextCount -= count;
            String name = realName;
            String changeRateStr = "-";
            String averageDayStr = "-";
            //name展示文案处理：“ 输单” 和 “取消” 两个阶段已经在上面处理完了，所以下面只要判断是不是“赢单”就行了
            if (!successTypeList.contains(type)) {
                changeRateStr = "0%";
                //当前阶段以及该阶段后面的所有总数
                int nowAndNextAllCount = nextCount + count;
                if (nowAndNextAllCount > 0) {
                    // 转化率（转化到下一阶段的成功率） = 该阶段后面所有阶段机会总数（乘以100，前端显示直接拼“%”即可）/ 当前时间段内所有机会作为该阶段的转化率
                    changeRateStr = Arith.div(nextCount * 100d, nowAndNextAllCount, 2) + "%";
                }
                Double day = dayMap.get(code);
                averageDayStr = day == null ? "-" : String.valueOf(day);
                name += String.format(I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_STAGE_CONTENT_3), count, averageDayStr, amountStr, unit);
            } else {//赢单
                name += String.format(I18nMessageUtil.getMessage(ChartI18Constant.OPPORTUNITY_STAGE_CONTENT_4), count, amountStr, unit);
            }
            int value;
            if (i == 0) {
                value = 100;
            } else if (i < size) {
                value = 100 - average * i;
            } else {
                value = 100 - flagValue;
            }
            flagValue = value;
            //置入穿透需要的阶段code
            throughDto.setDictionaryCodeThrough(code);
            String through = throughHelp.getThrough(throughDto, timePojo);
            ResultDataSeriesChartVO dataSeries = new ResultDataSeriesChartVO(name, realName, code, String.valueOf(value));
            dataSeries.setThrough(through);
            dataSeriesList.add(dataSeries);
            totalCount = totalCount + count;
            BigDecimal amountNumber = BigDecimal.valueOf(amount);
            totalAmount = totalAmount.add(amountNumber);
            BigDecimal predictionAmountNumber = BigDecimal.valueOf(predictionAmount);
            totalPredictionAmount = totalPredictionAmount.add(predictionAmountNumber);
            ResultDataTableVO nameVo = new ResultDataTableVO(realName, null);
            ResultDataTableVO countVo = new ResultDataTableVO(String.valueOf(count), through);
            ResultDataTableVO amountVo = new ResultDataTableVO(amountStr, through);
            ResultDataTableVO winRateVo = new ResultDataTableVO(winRate, null);
            ResultDataTableVO predictionAmountVo = new ResultDataTableVO(predictionAmountStr, through);
            ResultDataTableVO changeRateVo = new ResultDataTableVO(changeRateStr, null);
            ResultDataTableVO averageDayVo = new ResultDataTableVO(averageDayStr, null);
            List<ResultDataTableVO> resultDataTableVOs = Arrays.asList(nameVo, countVo, amountVo, winRateVo, predictionAmountVo, changeRateVo, averageDayVo);
            resultDataTableVOList.add(resultDataTableVOs);
        }
        // 总计
        ResultDataTableVO nameVo = new ResultDataTableVO(I18nMessageUtil.getMessage(CommonConstant.TOTAL), null);
        ResultDataTableVO opportunityNumVo = new ResultDataTableVO(String.valueOf(totalCount), null);
        ResultDataTableVO opportunityAmountVo = new ResultDataTableVO(String.valueOf(ChartResultUtil.formatVal4Num(totalAmount, numFormatEnum)), null);
        ResultDataTableVO opportunity1Vo = new ResultDataTableVO(null, null);
        ResultDataTableVO predictionAmountVo = new ResultDataTableVO(String.valueOf(ChartResultUtil.formatVal4Num(totalPredictionAmount, numFormatEnum)), null);
        ResultDataTableVO opportunity2Vo = new ResultDataTableVO(null, null);
        ResultDataTableVO opportunity3Vo = new ResultDataTableVO(null, null);
        List<ResultDataTableVO> totalVOs = Arrays.asList(nameVo,opportunityNumVo,opportunityAmountVo,opportunity1Vo,predictionAmountVo,opportunity2Vo,opportunity3Vo);
        resultDataTableVOList.add(totalVOs);
        resultChartVO.setDataSeries(dataSeriesList);
        resultChartVOList.add(resultChartVO);
        resultTableVO.setData(resultDataTableVOList);
        resultTableVO.setTableName("销售漏斗");
        chartResultDetailVO.setChart(resultChartVOList);
        chartResultDetailVO.setTable(resultTableVO);
        return chartResultDetailVO;
    }
    /**
     * 平均停留时长
     * 分两种情况：
     * （1）还处于该阶段，则按照截至到nowTime（当前时间）减去进入该阶段的时间，差值即停留时长；
     * （2）已经改到下面的阶段，则直接按照离开阶段的时间减去进入到该阶段时的时间，差值即停留时长。
     * @param chartResultPojo 参数pojo
     * @param saleStageIdIn 要查询的阶段ids
     * @return java.util.Map<java.lang.String,java.lang.Double>
     * @throws XbbException
     * @author zcp
     * @date 2019/3/10 16:25
     * @since v1.0
     * @version v1.0
     */
    private Map<String, Double> getAverageStayTime(ChartResultPojo chartResultPojo, List<String> saleStageIdIn) throws XbbException {
//        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQuery4Create(chartResultPojo, fieldTime, true, saleStageIdIn, false);
        //es 查询条件封装
        BoolQueryBuilder boolQueryBuilder = chartQueryHelp.opportunityQueryBaseForSaleFunnel(chartResultPojo, true, saleStageIdIn);
        //数据量超限（SizeConstant.BI_MUITI_DATA_LIMIT），不处理
        long count = paasEsModel.count(boolQueryBuilder, IndexTypeEnum.IDX_SAAS_OPPORTUNITY);
        if(count > SizeConstant.BI_MUITI_DATA_LIMIT){
            return new HashMap<>(BasicConstant.ONE);
        }
        List<String> fieldList = Arrays.asList(FieldTypeEnum.DATAID.getAlias());
        List<PaasFormDataEntityExt> list = esHelper.findByScroll(IndexTypeEnum.IDX_SAAS_OPPORTUNITY, boolQueryBuilder, PaasFormDataEntityExt.class, fieldList);
        Set<Long> opportunityIdSet = new HashSet<>();
        for (PaasFormDataEntityExt entityExt: list){
            opportunityIdSet.add(entityExt.getDataId());
        }
        return opportunityStageLogModel.getAverageStayTime(chartResultPojo.getCorpid(), opportunityIdSet, saleStageIdIn);
    }
}
